using System;
using System.Collections;
using System.Globalization;
using System.Text;
using Memcached.ClientLibrary;
using NWrapper.Cache;
using NWrapper.Config;

namespace NWrapper.Cache.Memcached
{
	/// <summary>
	/// Implementation of a cache that uses the memcacheddotnet client
	/// (https://sourceforge.net/projects/memcacheddotnet/).
	/// Using this library requires the inclusion of log4net and ICSharpCode.SharpZipLib 
	/// assemblies in your project.
	/// </summary>
	/// <remarks>
	/// Memcached serializes all data stored in the cache, so a retrieved object will not be the
	/// same object reference as the stored object.
	/// 
	/// Configuration settings can be applied by using the custom section of your configuration file.
	/// See the <see cref="Initialize()"/> method for more details.
	/// </remarks>
	public class MemcachedCache : ICache, IDisposable
	{
		private SockIOPool _pool;
		private MemcachedClient _cache;

		/// <summary>
		/// Default constructor.
		/// </summary>
		public MemcachedCache()
		{
		}

		#region ICache Members
		/// <summary>
		/// Initializes the underlying cache.
		/// </summary>
		/// <remarks>
		/// Custom settings can be set to configure the Memcached client, where:
		/// 
		/// <list>
		/// <item>servers is a comma-separated list of IP addresses and port numbers (default: 127.0.0.1:11211)</item>
		/// <item>enableCompression is true or false (default: false)</item>
		/// </list>
		/// 
		/// <code>
		/// <![CDATA[
		/// <NWrapper logger="None">
		///   <cache type="Memcached">
		///     <custom>
		///       <add key="poolName" value="NWrapper" />
		///       <add key="servers" value="127.0.0.1:11211,192.168.1.1:20001" />
		///       <add key="enableCompression" value="true" />
		///     </custom>
		///   </cache>
		/// </NWrapper>
		/// ]>
		/// </code>
		/// </remarks>
		/// <param name="settings">The cache settings.</param>
		public void Initialize(CacheSettings settings)
		{
			string poolName = (settings.Custom["poolName"] != null ? settings.Custom["poolName"].Value as string : "NWrapper");
			string configservers = (settings.Custom["servers"] != null ? settings.Custom["servers"].Value as string : "127.0.0.1:11211");
			string[] servers = configservers.Split(',');

			_pool = SockIOPool.GetInstance(poolName);
			_pool.SetServers(servers);
			_pool.Initialize();

			_cache = new MemcachedClient();
			_cache.PoolName = poolName;

			string enableCompression = (settings.Custom["enableCompression"] != null ? settings.Custom["enableCompression"].Value as string : "false");
			_cache.EnableCompression = String.Equals(enableCompression, "true"); ;
		}

		/// <summary>
		/// Gets or sets the cache item with the specified object key.
		/// </summary>
		/// <param name="key">The key of the cache item.</param>
		/// <returns>The cache item's value.</returns>
		public object this[object key]
		{
			get
			{
				return this.Get(key);
			}
			set
			{
				this.Set(key, value);
			}
		}

		/// <summary>
		/// Gets or sets the cache item with the specified string key.
		/// </summary>
		/// <param name="key">The key of the cache item.</param>
		/// <returns>The cache item's value.</returns>
		public object this[string key]
		{
			get
			{
				return this.Get(key);
			}
			set
			{
				this.Set(key, value);
			}
		}

		/// <summary>
		/// Retrieves an object from the cache.
		/// </summary>
		/// <param name="key">The key of the object.</param>
		/// <returns>The cached object.</returns>
		public object Get(object key)
		{
			if (key == null)
				return null;

			return _cache.Get(CacheHelper.GetCacheKey(key));
		}

		/// <summary>
		/// Places an item in the cache.
		/// </summary>
		/// <param name="key">The key of the object.</param>
		/// <param name="value">The cached object.</param>
		public void Set(object key, object value)
		{
			if (key == null)
				throw new ArgumentNullException("key");

			_cache.Set(CacheHelper.GetCacheKey(key), value);
		}

		/// <summary>
		/// Place an item in the cache that will expire after the given time span.
		/// </summary>
		/// <param name="key">The key of the object.</param>
		/// <param name="value">The cached object.</param>
		/// <param name="timeToExpire">The <see cref="TimeSpan"/> to use for
		/// item expiration.</param>
		public void Set(object key, object value, TimeSpan timeToExpire)
		{
			if (key == null)
				throw new ArgumentNullException("key");

			_cache.Set(CacheHelper.GetCacheKey(key), value, DateTime.Now.Add(timeToExpire));
		}

		/// <summary>
		/// Place an item in the cache with the specified cache expiration.
		/// </summary>
		/// <param name="key">The key of the object.</param>
		/// <param name="value">The cached object.</param>
		/// <param name="expiration">The <see cref="DateTime"/> to use for
		/// item expiration.</param>
		public void Set(object key, object value, DateTime expiration)
		{
			if (key == null)
				throw new ArgumentNullException("key");

			_cache.Set(CacheHelper.GetCacheKey(key), value, expiration);
		}

		/// <summary>
		/// Determines whether an object with the given key exists in the cache.
		/// If the ASP.Net cache is unavailable, this will always return false.
		/// </summary>
		/// <param name="key">The key of the object.</param>
		/// <returns>True if the key exists in the cache, false otherwise.</returns>
		public bool Contains(object key)
		{
			return (this.Get(key) != null);
		}

		/// <summary>
		/// Removes an object from the cache. If the ASP.Net cache is unavailable, this
		/// will do nothing.
		/// </summary>
		/// <param name="key">The key of the object.</param>
		public void Remove(object key)
		{
			if (key != null)
			{
				_cache.Delete(CacheHelper.GetCacheKey(key));
			}
		}

		/// <summary>
		/// Removes all objects from the cache. If the ASP.Net cache is unavailable, this
		/// will do nothing.
		/// </summary>
		public void Clear()
		{
			_cache.FlushAll();
		}
		#endregion

		#region IDisposable Members
		/// <summary>
		/// Disposes the underlying cache.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(true);
		}
		/// <summary>
		/// Disposes the underlying cache.
		/// </summary>
		/// <param name="disposing">Identifies whether the object is being disposed.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				_pool.Shutdown();
				_pool = null;
			}
		}
		#endregion
	}
}
